﻿using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.Text;
using System.Linq;
using Org.BouncyCastle.Crypto.Parameters;
using Newtonsoft.Json.Linq;
using Org.BouncyCastle.Crypto.Digests;

namespace BlockchainCrypto
{
    class Keyfile
    {
        /// <summary>
        /// -name: keyfile
        /// -type: NORMAL
        /// -address:
        ///  mainnet: atp17adp9y9elxv2thewj35gx4y55g958fwgsnk88r
        ///  testnet: atx17adp9y9elxv2thewj35gx4y55g958fwg642d5f
        /// -public key: 0xc9fc2e129f96d71dfa6fd145d2c491b51d08c54c6f319ecf45702bd86edbcec77208d5d52fd9b6b25c445adeaa372dcbd6b7999c25d2feeb21634059a260cc7e



        /// **Important** write this Private Key in a safe place.
        /// It is the important way to recover your account if you ever forget your password.
        /// 2ff03df0cd560bc6e0dbba41e76ddde560ce6bda76346a7299073651a38ca1cc
        /// ** Important** write this mnemonic phrase in a safe place.
        /// It is the important way to recover your account if you ever forget your password.
        /// hybrid arrange check erupt destroy supply lobster mystery genius state snack truth
        /// 
        /// 
        /// 
        /// 
        /// keyfile.json
        /// 
        /// {
        ///   "address": {
        ///     "mainnet": "atp17adp9y9elxv2thewj35gx4y55g958fwgsnk88r",
        ///     "testnet": "atx17adp9y9elxv2thewj35gx4y55g958fwg642d5f"
        ///   },
        ///   "id": "a7d2c700-a315-4ba4-8f49-d6ad32c52163",
        ///   "version": 3,
        ///   "crypto": {
        ///     "cipher": "aes-128-ctr",
        ///     "ciphertext": "a33bd9fee63eabda7637f8c6bf0f4ebfe6e43b41cbfd808738979f83f68884f6",
        ///     "cipherparams": { "iv": "19f6a6462e658a9c9e22466196f884da" },
        ///     "kdf": "scrypt",
        ///     "kdfparams": {
        ///       "dklen": 32,
        ///       "n": 16384,
        ///       "p": 1,
        ///       "r": 8,
        ///       "salt": "4a9a80050263d03b48a093252b69404f601506bc6261aebd10bda4c982959019"
        ///     },
        ///     "mac": "5e962fa9f85ff4b41f1ad4c0596d98cd082751eafaf07296322f1ab6868cfa9a"
        ///   }
        /// }
        /// </summary>
        public static void Main()
        {
            // 
            var password = "12345678";
            var comparisonBytes = GetPrivateKey(System.IO.File.ReadAllText("keyfile2.json"), password);
            Console.WriteLine(Hex.ToHexString(comparisonBytes));
        }

        private static byte[] GetPrivateKey(string keyfile, string password)
        {
            var json = JObject.Parse(keyfile);
            var kdfparams = json["crypto"]["kdfparams"];
            var salt = kdfparams["salt"].ToString();
            var dkLen = Convert.ToInt32(kdfparams["dklen"]);
            byte[] derivedKey = null;
            if (json["crypto"]["kdf"].ToString() == "scrypt")
            {
                var N = Convert.ToInt32(kdfparams["n"]);
                var r = Convert.ToInt32(kdfparams["r"]);
                var p = Convert.ToInt32(kdfparams["p"]);
                // scrypt
                derivedKey = SCrypt.Generate(Encoding.ASCII.GetBytes(password), Hex.Decode(salt), N, r, p, dkLen);
            }else if (json["crypto"]["kdf"].ToString() == "pbkdf2")
            {
                // 有时keyfile也会使用pdkdf2来做mac验证，如imtoken生成的keyfile就用pdkdf2
                var prf = kdfparams["prf"].ToString();
                if(prf != "hmac-sha256")
                {
                    throw new Exception("Unsupported key derivation scheme");
                }
                var c = Convert.ToInt32(kdfparams["c"]);
                // pbkdf2
                // pbkdf2与PKCS5S2关系：https://en.wikipedia.org/wiki/PBKDF2
                var gen = new Pkcs5S2ParametersGenerator(new Sha256Digest());
                gen.Init(Encoding.ASCII.GetBytes(password), Hex.Decode(salt), c);
                derivedKey = ((KeyParameter)gen.GenerateDerivedMacParameters(dkLen * 8)).GetKey();
            }
            byte[] ciphertext = Hex.Decode(json["crypto"]["ciphertext"].ToString());

            // keccak256 hash            
            var hashAlgorithm = new KeccakDigest(256);
            var inputList = derivedKey.Skip(16).Take(16).ToList();
            inputList.AddRange(ciphertext);
            //byte[] input = last16BytesOfDerivedKey.ToArray();
            hashAlgorithm.BlockUpdate(inputList.ToArray(), 0, inputList.Count);

            byte[] result = new byte[32]; // 512 / 8 = 64
            hashAlgorithm.DoFinal(result, 0);

            var mac = Hex.ToHexString(result);
            if (mac != json["crypto"]["mac"].ToString())
                throw new Exception("Key derivation failed - possibly wrong password");

            // aes-128-ctr decrypt
            var decipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");

            KeyParameter keyParameter = ParameterUtilities.CreateKeyParameter("AES", derivedKey.Take(16).ToArray());
            var cipherParameters = new ParametersWithIV(keyParameter, 
                Hex.Decode(json["crypto"]["cipherparams"]["iv"].ToString()), 0, 16);

            byte[] comparisonBytes = new byte[decipher.GetOutputSize(ciphertext.Length)];
            decipher.Reset();
            decipher.Init(false, cipherParameters);
            int length = decipher.ProcessBytes(ciphertext, comparisonBytes, 0);
            decipher.DoFinal(comparisonBytes, length); //Do the final block
            return comparisonBytes;
        }
    }
}
